//
//  JCMappingUtil.swift
//  JCPublicKit
//
//  Created by JasonLee on 2020/5/12.
//

import Foundation

/*
 使用示例:
 
 接口返回的数据:
 {
    code: 200,
    message: nil,
    data: {
       "code": 200,
       "message": null,
       "data": {
           "user_id" : 1,
           "token" : "1",
           "capability" : {
               "is_vip" : false,
               "is_admin" : true
           },
           "records" : [
               "login at 2020-05-13",
               "logout at 2020-05-12",
               "remove user with name: BLOCKED"
           ],
           "trust_point" : 999.9,
           "nickname" : "admin"
       }
    }
 }
 
 // 示例模型结构:
 struct TestModel: JCMappable {
     var userID: Int
     var token: String?
     var isVip: Bool = false
     var isAdmin: Bool = false
     var records: [String] = []
     var trustPoint: Double = 0
     var nickname: String
     
     init?(_ map: JCMap) {
         // 如果返回的数据里面不含有这些key值，将会返回nil
         guard map.has("user_id, token") else { return nil }
         
         userID     = map <! "user_id"
         token      = map <? "token"
         records    = map <! "records"
         trustPoint = map <! "trust_points"
         nickname   = map <? "nickname" ?? "请设置昵称"

         if let capibility: JCMap = map["capibility"] {
             isAdmin    = capibility <! "is_admin"
             isVip      = capibility <? "is_vip" ?? false
         }
     }
 }
 
 // 模拟接口请求返回处理方法，单个数据时
 func on(Success responseObject: Any?, _ callback: @escaping (TestModel?) -> Void) {
     guard
         let responseObject = responseObject as? [String : Any],
         let dataDict: [String : Any] = responseObject.jc_dictionary(for: "data") else { return }
 
     let testModel = TestModel(dataDict.jc_map)
     let testModelB: TestModel = dataDict.jc_convert()
 
     callback(testModel)
 }
 
 // 模拟接口请求返回处理方法，多个数据时
 func on(Success responseObject: Any?, _ callback: @escaping ([TestModel]) -> Void) {
    guard
        let responseObject = responseObject as? [String : Any],
        let dataArray: [[String : Any]] = responseObject.jc_array(for: "data") else { return }
 
    // 使用Array().jc_convert {} 方法将 字典 数组 转换为 数据模型数组
    let tempModels: [TestModel] = dataArray.jc_convert { TestModel($0.jc_map) }
 
    callback(tempModels)
 }
 */

/*
 * 依赖 JCUnwrapUtil
 * 非必须工具
 * 提供JCMap，用于将[String : Any]转换，并提供便捷方法实例化Model
 * 基本属于Dictionary的一个二次封装
 */
protocol JCMappable {
    init?(_ map: JCMap)
}

class JCMap: NSObject {
    /// 数据源
    fileprivate var dataSource: [String : Any]
    
    init(dataSource: [String : Any]) {
        self.dataSource = dataSource
    }
    
    subscript<T>(key: String) -> T? {
        return self.dataSource.jc_get(for: key)
    }
    subscript(key: String) -> JCMap? {
        guard let dict: [String : Any] = self.dataSource.jc_dictionary(for: key) else {
            return nil
        }
        return JCMap(dataSource: dict)
    }
    
    /**
     判断数据源中是否存在下列键值
     */
    func has(_ keys: String...) -> Bool {
        for key in keys {
            if !self.dataSource.keys.contains(key) {
                return false
            }
        }
        return true
    }
}

extension Dictionary where Key == String, Value: Any {
    var jc_map: JCMap {
        return JCMap(dataSource: self)
    }
    func jc_convert<T: JCMappable>() -> T? {
        return T(self.jc_map)
    }
}
extension Array where Element == Dictionary<String, Any> {
    func jc_convert<T>(_ mapping: ((Dictionary<String, Any>) -> T?)) -> [T] {
        var tempArray = [T]()
        for dictionary in self {
            guard let model = mapping(dictionary) else { continue }
            tempArray.append(model)
        }
        return tempArray
    }
}


infix operator <?: AdditionPrecedence
infix operator <!: AdditionPrecedence

func <? <T: JCMappable>(lhs: JCMap, rhs: String) -> [T]? {
    guard let dictArray: [[String : Any]] = lhs.dataSource.jc_array(for: rhs) else { return nil }
    var tempArray = [T]()
    for dict in dictArray {
        if let tempData = T(dict.jc_map) {
            tempArray.append(tempData)
        }
    }
    return tempArray
}
func <! <T: JCMappable>(lhs: JCMap, rhs: String) -> [T] {
    var tempArray = [T]()
    guard let dictArray: [[String : Any]] = lhs.dataSource.jc_array(for: rhs) else { return tempArray }
    for dict in dictArray {
        if let tempData = T(dict.jc_map) {
            tempArray.append(tempData)
        }
    }
    return tempArray
}

func <? <T: JCMappable>(lhs: JCMap, rhs: String) -> T? {
    guard
        let instance = T(lhs)
        else { return nil }
    return instance
}

func <? <K, V>(lhs: JCMap, rhs: String) -> Dictionary<K, V>? {
    let dictionary: Dictionary<K, V>? = lhs.dataSource.jc_dictionary(for: rhs)
    return dictionary
}
func <! <K, V>(lhs: JCMap, rhs: String) -> Dictionary<K, V> {
    return lhs.dataSource.jc_dictionary(for: rhs) ?? Dictionary<K, V>()
}

func <? <T>(lhs: JCMap, rhs: String) -> [T]? {
    let array: Array<T>? = lhs.dataSource.jc_array(for: rhs)
    return array
}
func <! <T>(lhs: JCMap, rhs: String) -> [T] {
    return lhs.dataSource.jc_array(for: rhs) ?? Array<T>()
}

func <? (lhs: JCMap, rhs: String) -> String? {
    return lhs.dataSource.jc_string(for: rhs)
}
func <! (lhs: JCMap, rhs: String) -> String {
    return lhs.dataSource.jc_string(for: rhs) ?? ""
}

func <? (lhs: JCMap, rhs: String) -> Bool? {
    return lhs.dataSource.jc_bool(for: rhs)
}
func <! (lhs: JCMap, rhs: String) -> Bool {
    return lhs.dataSource.jc_bool(for: rhs) ?? false
}

func <? (lhs: JCMap, rhs: String) -> Int? {
    return lhs.dataSource.jc_int(for: rhs)
}
func <! (lhs: JCMap, rhs: String) -> Int {
    return lhs.dataSource.jc_int(for: rhs) ?? 0
}

func <? (lhs: JCMap, rhs: String) -> Double? {
    return lhs.dataSource.jc_double(for: rhs)
}
func <! (lhs: JCMap, rhs: String) -> Double {
    return lhs.dataSource.jc_double(for: rhs) ?? 0.0
}

func <? (lhs: JCMap, rhs: String) -> CGFloat? {
    if let value = lhs.dataSource.jc_double(for: rhs) {
        return CGFloat(value)
    }
    return nil
}
func <! (lhs: JCMap, rhs: String) -> CGFloat {
    if let value = lhs.dataSource.jc_double(for: rhs) {
        return CGFloat(value)
    }
    return 0
}
